package com.web_bloks.document.client;

import com.google.gwt.http.client.RequestException;

import java.util.Iterator;
import java.util.Vector;

import com.web_bloks.system.client.Logger;


/**
 * DocumentManager
 * 1) Loads a page through:
 * - local source   i.e. Gears database
 * - remote source  i.e. origin server  through the /page/PAGE-X/ interface
 * 
 * 2) Loads a 'raw' page through:
 * - local source
 * - remote source  i.e. /rawpage/PAGE-X/
 * 
 * 3) Caches page/raw page 
 * 
 * CONFIGURATION REQUIRED:
 * a) listeners (DocumentListener)
 * b) API access point
 * c) [optional] timeout
 * 
 * @author Jean-Lou Dupont
 *
 */
public class DocumentManager implements DocumentLoaderListener {

	public final static int DEFAULT_TIMEOUT = 24*60*60*1000;
	
	public final static String CONTENT_TYPE = "text/html";

	//Gears related
	final static String PAGE_STORE_NAME = "page";
	
	// HTTP API related
	int timeout = DEFAULT_TIMEOUT;
	boolean busy = false;
	
	GearsDocumentStore documentStore = new GearsDocumentStore( PAGE_STORE_NAME );
	
	DocumentLoader documentLoader = new DocumentLoader();
	
	Vector<DocumentListener> documentListeners = new Vector<DocumentListener>();
	
	boolean storeAvailable = false;
	
	boolean storeDisable   = true;
	
	/*===================================================================
	 * Singleton 
	 ===================================================================*/
	public DocumentManager() {
		documentLoader.addListener(this);
	}
	public void init(boolean storeDisable) {
		this.storeDisable = storeDisable;
		
		if (!storeDisable)
			try {
				storeAvailable = documentStore.initialize();
			} catch(DocumentStoreException e) {
				storeAvailable = false;
				Logger.logError(getClass()+": exception raised whilst Initializing datastore. Msg: " +e.getMessage() );
			}		
	}
	/*===================================================================
	 * API 
	 ===================================================================*/
	/**
	 * HTTP API access point
	 */
	public void setAPI(String api) {
		documentLoader.setAPI( api );
	}
	
	public void setTimeout(int timeoutInMillis) {
		timeout = timeoutInMillis;
	}
	
	public boolean isBusy() {
		return documentLoader.isPending();
	}
	
	/**
	 * Loads a document and notifies listeners of the
	 * pending change.
	 * 
	 * @param name
	 * @param targetRegionName
	 */
	public void load(String name) {
		load( name, "" );
	}
	
	/**
	 * 
	 * @param name String page-name
	 * @param targetRegionName
	 */
	public void load( String name, String targetRegionName ) {
		
		notifyListeners_documentChanging( documentListeners, name, targetRegionName );
		
		Document doc = null;
		if (storeAvailable) {
			try {
				doc = documentStore.get(name);
			} catch(DocumentStoreException e) {
				doc = null;
				Logger.logError(getClass()+".load: exception raised whilst GETting from datastore. Msg: " +e.getMessage() );
			}
		}
		// no hit from the cache...
		if ( doc == null ) {
			try {
				documentLoader.load(name, this.timeout);
			} catch(RequestException e) {
				Logger.logError(getClass()+".load: exception raised whilst GETting using documentLoader. Msg: " +e.getMessage() );
			}
			return;
		}
		
		//TODO fix this.... revalidation   
		
		// got from the cache... send it along
		// once the "must-revalidate" test is performed:
		
		// Cache-Control: must-revalidate  ==> conditional get
		if (doc.getMeta("Cache-Control").indexOf("must-revalidate") != -1) {
			
			// send the current best document: better than nothing if the
			// revalidation process fails
			this.notifyListeners_documentChanged(documentListeners, doc);
			
			Logger.logWarn(getClass()+".load: must-revalidate");
			try {
				documentLoader.loadC(name, doc.getMeta("Last-Modified"));
			} catch(RequestException e) {
				Logger.logError(getClass()+".load: exception raised whilst GETting using documentLoader. Msg: " +e.getMessage() );
			}
		} else {
			// send it along now
			this.notifyListeners_documentChanged(documentListeners, doc);
		}
			
	}
	/**
	 * Invalidates the local cache
	 * @param name
	 */
	public void invalidateCache( String name ) {
		if (!storeAvailable)
			return;

		try {
			documentStore.delete(name);
		} catch( DocumentStoreException e ) {
			Logger.logError(getClass()+".invalidateCache: exception raised whilst processing page["+name+"]");
		}
	}
	/**
	 * Clears the entire local cache
	 */
	public void clear() {
		if (!storeAvailable)
			return;
		
		try {
			documentStore.clear();
		} catch(DocumentStoreException e) {
			Logger.logError(getClass()+".clear: exception raised, msg="+e.getMessage());
		}
	}
	/**
	 * Clears a complete namespace from the local cache
	 * @param ns
	 */
	public void clearNamespace(Namespace ns) {
		if (!storeAvailable)
			return;
		
		try {
			documentStore.clearNamespace(ns);
		} catch( DocumentStoreException e ) {
			Logger.logError(getClass()+".clearNamespace: exception raised, msg=" + e.getMessage());
		}
		
	}
	/*===================================================================
	 * DocumentLoaderListener
	 ===================================================================*/
	
	public void onDocumentLoaderResponse(Document doc, DocumentLoaderResponse code) {
		if ( !code.compareCode(DocumentLoaderResponse.OK ) )
			Logger.logError(getClass()+".onDocumentLoaderResponse: error encountered");
		else { 
			// Logger.logInfo(getClass()+".onDocumentLoaderResponse: OK");
			// Save to local data-store... if allowed to.
			// Cache-Control: no-cache
			if (doc.getMeta("Cache-Control").indexOf("no-cache") == -1) {
				if (storeAvailable) {
					try {
						documentStore.put(doc);
						Logger.logInfo(getClass()+".onDocumentLoaderResponse: store OK");
					} catch(DocumentStoreException e) {
						Logger.logError(getClass()+".load: exception raised whilst PUTting document in store. Msg: " +e.getMessage() );				
					}
				}
			} else {
				Logger.logInfo(getClass()+".onDocumentLoaderResponse: no-cache received");
			}
		}
		
		this.notifyListeners_documentChanged(documentListeners, doc);
	}
	
	/*===================================================================
	 * LISTENERS
	 ===================================================================*/
	public void addDocumentChangedListener(DocumentListener listener) {
		documentListeners.add(listener);
	}

	public void removeDocumentChangedListener(DocumentListener listener) {
		documentListeners.remove(listener);
	}
	
	/*===================================================================
	 * NOTIFICATIONS - DocumentListeners
	 ===================================================================*/
	protected void notifyListeners_documentChanged(Vector<DocumentListener> listeners, Document doc) {
		Logger.logInfo(getClass()+".notifyListeners: begin");
		Iterator<DocumentListener> it = listeners.iterator();
		boolean rendered = false;
		boolean announce_rendering = false;
		while (it.hasNext()) {
			Object o = it.next ();
		    rendered = ((DocumentListener) o).onDocumentChanged( doc );
		    if (rendered)
		    	announce_rendering = true;
		}
		
		// Announce the successful rendering of the document
		if (announce_rendering)
			notifyListeners_documentRendered( listeners, doc );
		
		//Logger.logInfo(getClass()+".notifyListeners: begin");
	}
	
	protected void notifyListeners_documentChanging(
			Vector<DocumentListener> listeners, String docName, String targetRegionName) {
		
		Logger.logInfo(getClass()+".notifyListeners_documentChanging: begin");
		
		Iterator<DocumentListener> it = listeners.iterator();
		while (it.hasNext()) {
			Object o = it.next ();
		    ((DocumentListener) o).onDocumentChanging( docName, targetRegionName );
		}
		//Logger.logInfo(getClass()+".notifyListeners: begin");
	}
	
	protected void notifyListeners_documentRendered(
			Vector<DocumentListener> listeners, Document doc ) {
		
		Logger.logInfo(getClass()+".notifyListeners_documentRendered: begin");
		
		Iterator<DocumentListener> it = listeners.iterator();
		while (it.hasNext()) {
			Object o = it.next ();
		    ((DocumentListener) o).onDocumentRendered( doc );
		}
		//Logger.logInfo(getClass()+".notifyListeners: begin");
	}
	
}//END